﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Collections;
using System.Collections.Concurrent;

using King.Extensions;
using King.Collections;
using King.Reflection;

namespace King.Loader {

    public abstract class LoaderException : Exception {
        internal LoaderException(string message) : base(message) { }
    }
    public sealed class LoaderResolutionException : LoaderException {
        private object m_key;
        private string m_message;

        public LoaderResolutionException(string message, object key)
            : base(message) {
            m_message = message;
            m_key = key;
        }

        public object Key {
            get { return m_key; }
        }
    }
    public sealed class LoaderDuplicateKeyException : Exception {
        private object m_key;
        private object m_existingValue;
        private object m_newValue;

        public LoaderDuplicateKeyException(
            object key, object existingValue, object newValue)
            : base("Key '{0}' of value '{1}' cannot be assigned to value '{2}' because is assigned to value '{3}'."
                .Substitute(key, key.GetType().Name, newValue, existingValue)) {

            m_key = key;
            m_existingValue = existingValue;
            m_newValue = newValue;
        }

        public object ExistingKey {
            get { return m_key; }
        }
        public object ExistingValue {
            get { return m_existingValue; }
        }
        public object NewValue {
            get { return m_newValue; }
        }
    }

    public sealed class LoaderFactory<K, V>
        where V : class {
        private Loader<K, V> m_loader;
        private K m_normalizedKey;

        internal LoaderFactory(Loader<K, V> loader, K normalizedKey) {
            m_loader = loader;
            m_normalizedKey = normalizedKey;
        }

        public K Key {
            get { return m_normalizedKey; }
        }
        public Loader<K, V> Loader {
            get { return m_loader; }
        }
        public int Order {
            get { return m_loader.GetOrder(m_normalizedKey); }
        }
    }

    /// <summary>A self populating dictionary where values typically have many keys.</summary>
    /// <typeparam name="V">Value type of the dictionary</typeparam>
    public sealed class Loader<K, V> 
        where V : class {

        private readonly object m_loaderLock;

        // templates
        private readonly Func<object, V> m_factory;
        private readonly Func<V, Loader<K, V>> m_getLoader;
        private readonly Func<V, IEnumerable<object>> m_getKeys;
        private readonly Lazy<Loader<K, V>> m_baseType;
        private readonly Func<LoaderFactory<K, V>, V> m_defaultResolver;
        private readonly Func<V, V> m_getOverride;

        private readonly List<Func<LoaderFactory<K, V>, V>> m_resolvers;

        // key could be a string, XName, XsdName, etc
        private readonly Dictionary<object, V> m_valuesByKey;
        private readonly Lazy<Dictionary<K, int>> m_orderByNormalizedKey;

        public Loader(
            Func<V, Loader<K, V>> getLoader,
            Func<object, K> normalizeKey,
            Func<V, IEnumerable<object>> getKeys,
            Func<Loader<K, V>, IEnumerable<object>> keys = null,
            Func<LoaderFactory<K, V>, V> defaultResolver = null,
            Func<Loader<K, V>, Loader<K, V>> getBaseType = null,
            Func<V, V> getOverride = null) {

            if (getLoader == null || normalizeKey == null || getKeys == null)
                throw new ArgumentNullException();

            m_loaderLock = new object();
            m_defaultResolver = defaultResolver;
            m_resolvers = new List<Func<LoaderFactory<K, V>, V>>();
            m_factory = o => {
                // normalize key
                var key = normalizeKey(o);
                if (key == null)
                    return null;

                // raise resolve event
                var loaderFactory = new LoaderFactory<K, V>(this, key);
                foreach (var resolver in m_resolvers) {
                    var value = resolver(loaderFactory);
                    if (value != null)
                        return value;
                }

                // try default resolver
                if (m_defaultResolver != null)
                    return m_defaultResolver(loaderFactory);

                return null;
            };

            m_getLoader = getLoader;
            m_getKeys = getKeys;

            m_getOverride = getOverride;
            m_baseType = new Lazy<Loader<K, V>>(() => 
                getBaseType == null ? null : getBaseType(this));

            m_valuesByKey = new Dictionary<object, V>();

            m_orderByNormalizedKey = new Lazy<Dictionary<K, int>>(() => {
                if (keys == null)
                    return new Dictionary<K, int>();

                var normalizedKeys = keys(this).Select(o => 
                    normalizeKey(o)).WhereNotDefault().ToArray();

                var result = normalizedKeys
                    .Select((o, i) => new { NormalizedKey = o, Order = i })
                    .ToDictionary(o => o.NormalizedKey, o => o.Order);

                return result;
            });
        }

        private void IfOtherLoaderThrow(V value) {
            var scope = m_getLoader(value);
            if (scope != this)
                throw new ArgumentException("T is not owned by this loader.");
        }
        private IEnumerable<V> Values(HashSet<V> overrides, object targetKey = null, bool declaredOnly = false) {

            // all keys or one key
            IEnumerable keys = m_orderByNormalizedKey.Value.Keys;
            if (targetKey != null)
                keys = new[] { targetKey };

            foreach (var key in keys) {

                // attempt resolution
                var value = TryGet(key, declaredOnly: true);

                // skip keys that do not resolve
                if (value == null)
                    continue;

                // yield result if it's not overridden
                if (!overrides.Contains(value))
                    yield return value;

                // can we resolve overrides?
                if (m_getOverride != null) {

                    // remember override so we can skip it when found in the inherited values
                    var overridenValue = m_getOverride(value);
                    if (overridenValue != null)
                        overrides.Add(overridenValue);
                }
            }

            // can\should we return inherited values?
            var baseType = m_baseType.Value;
            if (baseType == null || declaredOnly)
                yield break;

            // return inherited values
            foreach (var inheritedValue in baseType.Values(overrides, targetKey, declaredOnly))
                yield return inheritedValue;
        }

        internal int GetOrder(K normalizedKey) {
            return m_orderByNormalizedKey.Value[normalizedKey];
        }

        public event Func<LoaderFactory<K, V>, V> Resolve {
            add {
                lock (m_loaderLock) {
                    if (m_valuesByKey.Any())
                        throw new InvalidOperationException(
                            "Handlers must be added before any values have been loaded.");

                    m_resolvers.Add(value);
                }
            }
            remove {
                throw new InvalidOperationException();
            }
        }
        public V Get(object key, bool declaredOnly = false) {
            var t = TryGet(key, declaredOnly);
            if (t != null)
                return t;
         
            throw new ArgumentException("Key '" + key + "' failed to resolve.");
        }
        public V TryGet(object key, bool declaredOnly = false) {
            if (key == null)
                return null;

            // key already a value
            if (key is V)
                return (V)key;
            
            lock (m_loaderLock) {

                // key not built-in value
                var value = m_valuesByKey.GetValueOrDefault(key);
                if (value != null)
                    return value;
            }

            // try to activate value
            V newValue = m_factory(key);

            // try to inherit value
            if (newValue == null && !declaredOnly && m_baseType.Value != null)
                return m_baseType.Value.TryGet(key);

            // failed to resolve
            if (newValue == null)
                return null;

            // verify one of new value's keys is the key being resolved
            var newKeys = m_getKeys(newValue).ToArray();
            if (newKeys.None(o => object.Equals(key, o)))
                throw new ArgumentException(
                    "A value resolved for key '" + key + "' does not identify itself with that key.");

            // verify value belongs to this loader
            IfOtherLoaderThrow(newValue);

            lock (m_loaderLock) {

                // try again
                var t = m_valuesByKey.GetValueOrDefault(key);
                if (t != null)
                    return t;

                // verify keys are unique among previously loaded yptes
                foreach (var newKey in newKeys.WhereNotDefault()) {
                    var existingValue = m_valuesByKey.GetValueOrDefault(newKey);
                    if (existingValue != null)
                        throw new LoaderDuplicateKeyException(
                            key: newKey,
                            existingValue: existingValue,
                            newValue: newValue);
                }

                // register t by its keys
                foreach (var o in newKeys.WhereNotDefault())
                    m_valuesByKey[o] = newValue;

                return newValue;
            }
        }
        public IEnumerable<V> Values(object key = null, bool declaredOnly = false) {
            return Values(new HashSet<V>(), key, declaredOnly);
        }
    }
}
